﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using OpenTK;
using OpenTK.Input;
using System.Runtime.InteropServices;
using System.IO;

#if EMBEDDED
    using OpenTK.Graphics.ES20;
#else
using OpenTK.Graphics.OpenGL;
#endif

namespace MonoStrategy.RenderSystem
{
    public partial class TerrainRenderer
    {
        public void OnRender()
        {
            if (PrecisionTimerCallback == null)
                throw new InvalidOperationException("No precision timer available.");

            Int64 animTime = PrecisionTimerCallback();

            lock (m_WorkItems)
            {
                LinkedListNode<WorkItem> list = m_WorkItems.First;

                while (list != null)
                {
                    LinkedListNode<WorkItem> next = list.Next;

                    if (list.Value.ExpirationMillis <= animTime)
                    {
                        m_WorkItems.Remove(list.Value);

                        list.Value.Task();
                    }

                    list = next;
                }
            }

            // compute visible area of the map
            GL.Disable(EnableCap.DepthTest);

            ScreenBounds = ComputeOcclusion(m_ProjMatrix, m_ViewMatrix, m_ModelMatrix);

            // collect all visible renderables
            List<RenderableVisual> visibleObjects = new List<RenderableVisual>();

            lock (m_SceneObjects)
            {
                m_SceneObjects.CopyArea(ScreenBounds, visibleObjects);
            }

            GL.Enable(EnableCap.AlphaTest);
            GL.AlphaFunc(AlphaFunction.Greater, 0.0f);
            {
                UpdateObjectSelection(visibleObjects);
            }
            GL.Disable(EnableCap.AlphaTest);

            GL.Enable(EnableCap.DepthTest);
            GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
            GL.Enable(EnableCap.Blend);
            {
                RenderGroundPlane(m_ProjMatrix, m_ViewMatrix, m_ModelMatrix);
            }

            GL.Enable(EnableCap.AlphaTest);
            GL.AlphaFunc(AlphaFunction.Greater, 0.0f);
            GL.DepthFunc(DepthFunction.Lequal);
            {
                GL.Clear(ClearBufferMask.DepthBufferBit);

                m_2DSpriteProgram.Bind(m_ProjMatrix, m_ViewMatrix, m_ModelMatrix);

                RadixRenderSprites(visibleObjects);

                m_2DSceneProgram.Bind(m_ProjMatrix, m_ViewMatrix, m_ModelMatrix);
            }
            GL.Disable(EnableCap.AlphaTest);
            GL.Disable(EnableCap.Blend);

            GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
            GL.Enable(EnableCap.Blend);
            {
                // render level points
                if (IsBuildingGridVisible)
                {
                    float w = 0.7f, s = (1 - w) / 2;

                    foreach (var stack in m_BuildingPipeline)
                    {
                        stack.Clear();
                    }

                    for (int y = ScreenBounds.Y, height = Math.Min(Size - 1, ScreenBounds.Y + ScreenBounds.Height); y < height; y++)
                    {
                        for (int x = ScreenBounds.X, width = Math.Min(Size - 1, ScreenBounds.X + ScreenBounds.Width); x < width; x++)
                        {
                            int eval = Terrain.GetBuildingExpenses(x, y);

                            if (eval <= 1)
                                continue;

                            m_BuildingPipeline[Math.Min(m_BuildingGridTextures.Length - 1, eval - 2)].Push(new Point(x, y));
                        }
                    }

                    for (int i = 0; i < m_BuildingPipeline.Length; i++)
                    {
                        m_BuildingGridTextures[i].Bind();

                        GL.Begin(BeginMode.Quads);
                        {
                            foreach (var pos in m_BuildingPipeline[i])
                            {
                                DrawAtlasStripe(
                                    null,
                                    pos.X + s, pos.Y + s, Terrain.GetZShiftAt(pos.X, pos.Y),
                                    w, w,
                                    255, 255, 255, 255,
                                    0, 0, 1, 1);
                            }
                        }
                        GL.End();
                    }
                }
            }
            GL.Disable(EnableCap.Blend);
        }

        private void RadixRenderSprites(IEnumerable<RenderableVisual> visibleObjects)
        {
            long time = PrecisionTimerCallback();

            if (SpriteEngine == null)
            {
                // fallback for AnimationEditor
                foreach (var visual in visibleObjects)
                {
                    visual.SetAnimationTime(time);
                    visual.Render(RenderPass.Pass1_Shadow);
                }
            }
            else
            {
                // using high performance sprite engine
                SpriteEngine.BeginRadixRender();
                {
                    foreach (var visual in visibleObjects)
                    {
                        visual.SetAnimationTime(time);
                        visual.Render(RenderPass.Pass1_Shadow);
                    }
                }
                SpriteEngine.EndRadixRender();
            }
        }

        internal void DrawAtlas(
            RenderableVisual inVisual, 
            float inLeft, float inTop, 
            float inZShift, 
            float inWidth, float inHeight, 
            byte inRed, byte inGreen, byte inBlue, byte inAlpha, 
            float inTexX1, float inTexY1, float inTexX2, float inTexY2)
        {
            GL.Begin(BeginMode.Quads);
            {
                DrawAtlasStripe(inVisual, inLeft, inTop, inZShift, inWidth, inHeight, inRed,
                    inGreen, inBlue, inAlpha, inTexX1, inTexY1, inTexX2, inTexY2);
            }
            GL.End();
        }

        internal void DrawAtlasStripe(
            RenderableVisual inVisual,
            float inLeft, float inTop,
            float inZShift,
            float inWidth, float inHeight,
            byte inRed, byte inGreen, byte inBlue, byte inAlpha,
            float inTexX1, float inTexY1, float inTexX2, float inTexY2)
        {
            if (m_IsSelectionPass)
            {
                int selectionIndex = -1;

                m_SelectionPassResults.Add(inVisual);

                selectionIndex = m_SelectionPassResults.Count * SelectionGranularity;
                inRed = (byte)((selectionIndex & 0xFF));
                inGreen = (byte)((selectionIndex & 0xFF00) >> 8);
                inBlue = (byte)((selectionIndex & 0xFF0000) >> 16);
            }

            GL.Color4(inRed, inGreen, inBlue, inAlpha);

            if ((inVisual != null) && (inVisual.BuildingProgress < 1.0) && (inVisual.BuildingProgress >= 0.0))
            {
                float texYSize = (inTexY1 - inTexY2) * (float)inVisual.BuildingProgress;
                float offsetX = inHeight - inHeight * (float)inVisual.BuildingProgress;

                inTexY1 = inTexY2 + texYSize;

                GL.TexCoord2(inTexX1, inTexY1);
                GL.Vertex3(inLeft, inTop + offsetX, inZShift);
                GL.TexCoord2(inTexX2, inTexY1);
                GL.Vertex3(inLeft + inWidth, inTop + offsetX, inZShift);
                GL.TexCoord2(inTexX2, inTexY2); // 
                GL.Vertex3(inLeft + inWidth, inTop + inHeight, inZShift);
                GL.TexCoord2(inTexX1, inTexY2);
                GL.Vertex3(inLeft, inTop + inHeight, inZShift);
            }
            else
            {
                GL.TexCoord2(inTexX1, inTexY1);
                GL.Vertex3(inLeft, inTop, inZShift);
                GL.TexCoord2(inTexX2, inTexY1);
                GL.Vertex3(inLeft + inWidth, inTop, inZShift);
                GL.TexCoord2(inTexX2, inTexY2);
                GL.Vertex3(inLeft + inWidth, inTop + inHeight, inZShift);
                GL.TexCoord2(inTexX1, inTexY2);
                GL.Vertex3(inLeft, inTop + inHeight, inZShift);
            }
        }
    }
}
